﻿/* --------------------------------------------------------------
 * exMath (C) Laurens Ruijtenberg
 * 
 * This code is being distributed under the MS-PL license. Feel
 * free to contact me if you like this code.
 * -------------------------------------------------------------- */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using exMath.Common;

namespace exMath.Statistics
{
    public class BasicStatistics
    {      

        /// <summary>
        /// Get the mode of x double array.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double Mode(double[] Values)
        {
            double CurrentMode = 0.0;
            double PreviousNumber = 0.0;
            int Count = 0;
            int CurrentModeCount = 0;

            Array.Sort<double>(Values);

            foreach (double Value in Values)
            {
                if (Value != PreviousNumber)
                {
                    //in this algorithm we use the last known mode if multiple numbers
                    //occur the same amounts of time.
                    if (CurrentModeCount <= Count)
                    {
                        CurrentMode = PreviousNumber;
                        CurrentModeCount = Count;
                    }

                    Count = 0;
                }

                Count++;
                PreviousNumber = Value;
            }

            return CurrentMode;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DomainValues"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double Mode(double[] DomainValues, MathFunction Function)
        {
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);
            return Mode(Results);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DomainBegin"></param>
        /// <param name="DomainEnd"></param>
        /// <param name="Interval"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double Mode(double DomainBegin, double DomainEnd, double Interval, MathFunction Function)
        {
            double[] DomainValues = Functions.ToDomainIntervalArray(DomainBegin, DomainEnd, Interval);
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);

            return Mode(Results);
        }
        
        /// <summary>
        /// Get the mean/avarage value of the array.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double Mean(double[] Values)
        {
            double Sum = BasicOperations.Sum(Values);
            return (Sum / Values.Length);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DomainValues"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double Mean(double[] DomainValues, MathFunction Function)
        {
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);
            return Mean(Results);
        }

        
        /// <summary>
        /// Get the mean/average value of x Function applied to the specified domain range.
        /// </summary>
        /// <param name="Function">Function for each Function</param>
        /// <param name="DomainBegin">Begin of the domain</param>
        /// <param name="DomainEnd">End of the domain</param>
        /// <param name="Interval">Interval for each step within domain boundaries to perform Function</param>
        /// <returns>The mean value of the Function results.</returns>
        public static double Mean(double DomainBegin, double DomainEnd, double Interval, MathFunction Function)
        {
            double[] DomainValues = Functions.ToDomainIntervalArray(DomainBegin, DomainEnd, Interval);
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);

            return Mean(Results);
        }



        /// <summary>
        /// Calculates the sample standard deviation based on the input values.
        /// </summary>
        /// <param name="DomainValues">Values used to calculate the standard deviation.</param>
        /// <returns>The standard deviation.</returns>
        public static double StandardDeviation(double[] Values)
        {
            double Average = Mean(Values);
            double DevidedByN = 1.0 / ((double)Values.Length - 1);
            
            double Sum =    BasicOperations.Sum(Values, delegate(double x)
                            {
                                double deviation = x - Average;
                                return deviation * deviation; //return the square of the deviation
                            });

            return Math.Sqrt(DevidedByN * Sum);
        }

        /// <summary>
        /// Execute x Function for each value within specified domain values and computes the sample standard deviation based on those results.
        /// </summary>
        /// <param name="DomainValues">Domain values for Function.</param>
        /// <param name="Function">Function to be executed on each domain value.</param>
        /// <returns>The standard deviation.</returns>
        public static double StandardDeviation(double[] DomainValues, MathFunction Function)
        {
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);
            return StandardDeviation(Results);
        }

        /// <summary>
        /// Execute x Function at each interval within the domain range, and computes the sample standard deviation based on the results.
        /// </summary>
        /// <param name="DomainBegin">Begin of domain.</param>
        /// <param name="DomainEnd">End of domain.</param>
        /// <param name="Interval">Interval within domain.</param>
        /// <param name="Function">Function to execute for each interval.</param>
        /// <returns>The standard deviation.</returns>
        public static double StandardDeviation(double DomainBegin, double DomainEnd, double Interval, MathFunction Function)
        {
            double[] DomainValues = Functions.ToDomainIntervalArray(DomainBegin, DomainEnd, Interval);
            return StandardDeviation(DomainValues, Function);
        }

        /// <summary>
        /// Calculates the sample variance.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double Variance(double[] Values)
        {
            double mean = Mean(Values);
            double DevidedByN = 1.0 / ((double)Values.Length - 1);

            double sum = BasicOperations.Sum(Values, delegate(double x)
            {
                double value = x - mean;
                return value * value;
            });

            return DevidedByN * sum;
        }

        /// <summary>
        /// Calculates the population variance.
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double PopulationVariance(double[] Values)
        {
            double mean = Mean(Values);

            double sum = BasicOperations.Sum(Values, delegate(double x)
            {
                double value = x - mean;
                return value * value;
            });

            return sum / Values.Length;
        }

        /// <summary>
        /// Calculate correlation using the Pearson Coefficient.
        /// </summary>
        /// <param name="DomainValues"></param>
        /// <param name="RangeValues"></param>
        /// <returns></returns>
        public static double Correlation(double[] DomainValues, double[] RangeValues)
        {
            double DomainMean = Mean(DomainValues);
            double RangeMean = Mean(RangeValues);
            double DomainStandardDeviation = StandardDeviation(DomainValues);
            double RangeStandardDeviation = StandardDeviation(RangeValues);

            double ResultNumerator = BasicOperations.Sum(DomainValues, RangeValues, delegate(double x, double y)
            {
                return (x - DomainMean) * (y - RangeMean);
            });

            double ResultDenominator = (DomainValues.Length - 1) * DomainStandardDeviation * RangeStandardDeviation;

            if (ResultNumerator == 0.0 && ResultDenominator == 0.0)
                return 0.0;
            else
                return ResultNumerator / ResultDenominator;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DomainValues"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double Correlation(double[] DomainValues, MathFunction Function)
        {
            double[] Results = Functions.ExecuteFunction(DomainValues, Function);
            return Correlation(DomainValues, Results);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="DomainBegin"></param>
        /// <param name="DomainEnd"></param>
        /// <param name="Interval"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double Correlation(double DomainBegin, double DomainEnd, double Interval, MathFunction Function)
        {
            double[] DomainValues = Functions.ToDomainIntervalArray(DomainBegin, DomainEnd, Interval);
            return Correlation(DomainValues, Function);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="MaxAmount"></param>
        /// <param name="ActualAmount"></param>
        /// <returns></returns>
        public static double PercentageFromAmount(double MaxAmount, double ActualAmount)
        {
            return (ActualAmount / (MaxAmount * 0.01));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="MaxAmount"></param>
        /// <param name="Percentage"></param>
        /// <returns></returns>
        public static double AmountFromPercentage(double MaxAmount, double Percentage)
        {
            return MaxAmount * 0.01 * Percentage;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double Min(double[] Values)
        {
            double lowest = double.MaxValue;

            foreach (double Value in Values)
                if (Value < lowest) lowest = Value;

            return lowest;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Values"></param>
        /// <returns></returns>
        public static double Max(double[] Values)
        {
            double highest = double.MinValue;

            foreach (double Value in Values)
                if (Value > highest) highest = Value;

            return highest;
        }
    }
}
